package br.com.felix.fwt.ui;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import br.com.felix.fwt.error.ApplicationError;
import br.com.felix.fwt.log.LoggerFactory;
import br.com.felix.fwt.security.Profile;
import br.com.felix.fwt.servlet.FelixWebToolkitServlet;
import br.com.felix.fwt.servlet.FwtContext;
import br.com.felix.fwt.ui.css.Style;
import br.com.felix.fwt.ui.exception.ComponentRenderException;
import br.com.felix.fwt.ui.javascript.JavascriptListener;
import br.com.felix.fwt.util.IdGenerator;


/**
 * The Class BaseComponent is the base class for all of the framework's components.
 * It uses the composite pattern to represent the hierarchy of the components.
 * <p>
 * Although all componenents have the same properties, they don't behave exactly
 * the same way in every component.
 * For example: the name property defines the 'name' and 'id' HTML properties in the Button component.
 * But in the Panel component, it defines only the 'id' propert, since DIVs don't have names.
 * </p>
 * <p>
 * The style property is one that have very different behaviors among components. This is because
 * some components are implemented with more than one HTML component inside.
 * This is open source. Go figure it out.
 * </p>
 */
public abstract class BaseComponent implements Serializable {

	private static final long serialVersionUID = 7454153132581227306L;

	private PresentationMode presentationMode = PresentationMode.EDIT;
	
	private static final Logger logger = LoggerFactory.getLogger(BaseComponent.class);
	
	/** The childs of this component. */
	public List<BaseComponent>childs;
	
	/** The action listener. */
	public ActionListener actionListener;
	
	/** 
	 * The name of the component. 
	 * It must be not null if the component will have its value updated after a form submit.
	 * No checking is made.
	 * */
	private String name;
	
	/** The value. */
	private String value;
	
	/** The style. */
	protected String style;
	
	/** The javascripts. */
	public JavascriptListener[] javascripts;
	
	/** 
	 * The css class. If you do not define the cssClass, it will use a default value.
	 * The default value is fwt + componentname (lower case). For example: fwtbutton, fwtcombobox. 
	 * 
	 * */
	public String cssClass;

	/**
	 * The profile that the user must have in order to access the component.
	 * */
	private Profile requiredProfile;
	
	/**
	 * Instantiates a new base component. Sets the value to the empty String.
	 */
	public BaseComponent(){
	    this.value = "";
	}
	
	/**
     * Instantiates a new base component and sets initial value for value.
     * @param value the initial component's value.
     */
    public BaseComponent(String value){
        this.value = value;
    }
	
	/**
	 * Adds a child to this component. It is used basically with container components, like Panel.
	 * 
	 * @param child the child to be added.
	 */
	public void add(BaseComponent ... children){
		if(childs==null)childs = new ArrayList<BaseComponent>();
		for(BaseComponent child: children){
			childs.add(child);
		}
	}
	
	/**
     * Adds a child to this component. It is used basically with container components, like Panel.
     * 
     * @param child the child to be added.
     * @param index the order where the child will be added.
     */
    public void add(int index, BaseComponent child){
        if(childs==null)childs = new ArrayList<BaseComponent>();
        childs.add(index, child);
    }
	
	/**
	 * Removes a child from this component. It is used basically with container components, like Panel.
	 * 
	 * @param child the child to be removed.
	 */
	public void remove(BaseComponent child){
		if(childs==null)return;
		boolean removed = childs.remove(child);
		if (!removed){
			logger.warn(String.format("Could not remove component '%s'.", child.getName()));
		}
	}
	
	
	/**
     * Removes a child from this component. It is used basically with container components, like Panel.
     * 
     * @param index the index of the child to be removed.
     */
    public void remove(int index){
        if(childs==null)return;
        childs.remove(index);
    }
	
	/**
	 * Helper method to print the HTML code of the childs.
	 * Just to avoid rewriting it in every component.
	 * 
	 * @param out where the childs' code will be printed.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	protected void writeChilds(PrintWriter out) throws ComponentRenderException{
		if(childs!=null){
			for(BaseComponent child: childs){
				child.write(out);
			}
		}
	}
	
	
	/**
	 * Clear interface. Removes all childs.
	 */
	public void clearInterface(){
		if(childs!=null)childs.clear();
	}
	
	/**
	 * This method is where the component HTML implementation resides.
	 * Every component is responsible for "drawing itself" in HTML.
	 *  
	 * @param out where the HTML code will be printed.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public abstract void write(PrintWriter out) throws ComponentRenderException;
	
	
	/**
	 * Sets the value.
	 * 
	 * @param newValue the new value
	 */
	public void setValue(String newValue){
		this.value = newValue;
	}
	
	

	/**
	 * Sets the style.
	 * 
	 * @param style the new style
	 */
	public void setStyle(Style style){
		this.style = style.toString();
	}
	
	/**
	 * This method sets the javascripts that will be run, each with its own event.
	 * The name is a little bit confusing, since the parameter is actually of the
	 * type JavascriptListener. But the idea is what counts.
	 * 
	 * @param js the javascripts that this component can fire.
	 */
	public void setJavascripts(JavascriptListener ...js){
		this.javascripts = js;
	}





	
	
	public PresentationMode getPresentationMode() {
		return presentationMode;
	}

	public void setPresentationMode(PresentationMode presentationMode) {
		this.presentationMode = presentationMode;
		if(this.childs!=null){
			for(BaseComponent child:childs){
				child.setPresentationMode(presentationMode);
			}
		}
	}
	
	
	/**
	 * Security method. Returns true if the user has a profile that is allowed to see this component.
	 * @see #requiredProfile
	 * */
	protected boolean authorized(){
		if(requiredProfile == null)
			return true;
		Profile userProfile = FelixWebToolkitServlet.getUserProfile();
		if(userProfile == null || (getRequiredProfile() != null && !userProfile.authorizedWhenRequiredProfileIs(getRequiredProfile())))
			return false;
		return true;
	}

	
	/**
	 * Obtains a reference to the currently executing form (the top parent of this component).
	 * */
	protected Form getExecutingForm(){
		FwtContext ctx = FelixWebToolkitServlet.getFwtContext();
		return ctx.form;
	}
	
	/**
	 * Defines the user profile that is required to view this component.
	 * */
	public void setRequiredProfile(Profile profile) {
		this.requiredProfile = profile;
	}
	
	public Profile getRequiredProfile(){
		return requiredProfile;
	}
	
	/**
	 * Returns true if the user has inputted a valid data. False otherwise.
	 * Implemented only by input components.
	 * */
	public boolean validate(){
		return true;
	}
	
	/**
	 * Returns a list of errors of the user inputted data.
	 * */
	public List<ApplicationError> getValidationErrors(){
		return null;
	}
	
	
	/**
	 * Generates a "random" name for the component.
	 * @param idGen 
	 * */
	public void generateName(IdGenerator idGen){
		if (this.name == null) {
			int id = idGen.getNextId();
			name = getClass().getSimpleName() + (id);
		}
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((value == null) ? 0 : value.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		BaseComponent other = (BaseComponent) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		}
		else
			if (!name.equals(other.name))
				return false;
		if (value == null) {
			if (other.value != null)
				return false;
		}
		else
			if (!value.equals(other.value))
				return false;
		return true;
	}

	
	/**
	 * Returns the name of the component.
	 * */
    public String getName() {
        return name;
    }

    /**
     * Returns the value stored at the component.
     * */
    public String getValue() {
        return value;
    }

    
    /**
     * Sets the name of the component.
     * */
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * Returns true if the component is used to store form input data.
     * */
    public boolean isInputControl(){
        return false;
    }
}
